package org.lex.swing.table;

import java.awt.Font;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

public class TableColumnResizer implements TableModelListener {
	static final int DEFAULT_GAP = 12;
	private static final int DEFAULT_COLUMN_WIDTH = 100;
	// original table to operate
	private final JTable table;
	// is auto-resite enabled
	private boolean enabled = true;
	// extra gap to pad
	private int extraGap = DEFAULT_GAP;
	// fill JViewport width
	private boolean fillsViewportWidth = true;

	// XXX what about JTableHeader
	public TableColumnResizer(final JTable table) {
		this.table = table;
		// when TableModel data changed, notify me
		table.getModel().addTableModelListener(this);
		// when TableModel of table changed, notify me
		table.addPropertyChangeListener("model", new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent e) {
				if ("model".equals(e.getPropertyName())) {
					if (null != e.getOldValue())
						((TableModel) e.getOldValue()).removeTableModelListener(TableColumnResizer.this);
					((TableModel) e.getNewValue()).addTableModelListener(TableColumnResizer.this);
					debug("table model changed");
				}
			}
		});
		// if table is in a JScrollPane, when parent size changed, notify me
		if (table.getParent() instanceof JViewport)
			table.getParent().addComponentListener(new ComponentAdapter() {
				@Override
				public void componentResized(ComponentEvent e) {
					if (enabled && fillsViewportWidth)
						tableChanged(new TableModelEvent(table.getModel(), TableModelEvent.HEADER_ROW));
				}
			});
		table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		this.tableChanged(new TableModelEvent(table.getModel(), TableModelEvent.HEADER_ROW));
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
		this.tableChanged(new TableModelEvent(table.getModel(), TableModelEvent.HEADER_ROW));
	}

	public boolean isFillViewportWidth() {
		return fillsViewportWidth;
	}

	public void setFillViewportWidth(boolean fills) {
		if (this.fillsViewportWidth != fills) {
			this.fillsViewportWidth = fills;
			this.tableChanged(new TableModelEvent(table.getModel(), TableModelEvent.HEADER_ROW));
		}
	}

	public void tableChanged(TableModelEvent e) {
		if (true == this.enabled)
			doResize(e);
	}

	private void doResize(TableModelEvent e) {
		/*
		 * FIXME
		 * ���������¼��������е�Ԫ��Ѱ�Һ��ʿ�ȣ�ʵ����Ӧ�ø��TableModelEvent�������
		 * ����˵�һ����
		 */
		debug("auto-resizing...");
		this.resizeTableColumns(e);
	}

	private void resizeTableColumns(TableModelEvent e) {
		TableModel model = table.getModel();
		if (null == model)
			// no TableModel??
			return;
		TableColumnModel column = table.getColumnModel();
		if (null == column)
			// no TableColumnModel??
			return;
		Font font = table.getFont();
		if (null == font)
			// this happends sometimes
			return;
		int columnCount = table.getColumnCount();
		if (0 == columnCount)
			// no column, we don't need to resize table
			return;
		// --------------------
		int totalWidth = 0;
		int[] widths = new int[columnCount];
		for (int c = 0; c < columnCount; c++) {
			widths[c] = this.getMaxColumnRendererWidth(table, c);
			totalWidth += widths[c];
		}
		if (true == fillsViewportWidth && table.getParent() instanceof JViewport) {
			/*
			 * ��table��������JViewport��ʱ��������������п���ܺ�û��ռ��JViewport��
			 * ��ʱ�Ѷ���ռ�ƽ����䵽ÿһ�У�����ǡ��ռ�����JViewport�Ŀ��
			 */
			int aWidth = table.getParent().getWidth();
			debug("aWidth= ", aWidth, " totalWidth=", totalWidth);
			if (totalWidth < aWidth) {
				int extra = aWidth - totalWidth;
				float step = ((float) extra) / columnCount;
				float totalSteps = step;
				int totalASteps = 0;
				int aStep = 0;
				for (int i = 0; i < columnCount; i++) {
					aStep = Math.round(totalSteps - totalASteps);
					widths[i] += aStep;
					totalSteps += step;
					totalASteps += aStep;
				}
				debug("extra= ", extra, ", aExtra= ", totalASteps);
			}
		}
		for (int i = 0; i < columnCount; i++)
			column.getColumn(i).setPreferredWidth(widths[i]);
	}

	private int getMaxColumnRendererWidth(JTable table, int viewColumnIndex) {
		TableCellRenderer renderer = table.getCellRenderer(0, viewColumnIndex);
		if (null == renderer)
			return DEFAULT_COLUMN_WIDTH;
		TableModel model = table.getModel();
		int modelColumnIndex = table.convertColumnIndexToModel(viewColumnIndex);
		// �ƺ�����ֻ����"������ʾ�ı��Ԫ"�Ŀ�ȣ����ټ�����
		int rowCount = model.getRowCount();
		ProperWidth max = new MaxProperWidth();
		for (int r = 0; r < rowCount; r++) {
			try {
				max.addSample(renderer.getTableCellRendererComponent(table, model.getValueAt(r, modelColumnIndex), false, false, r, modelColumnIndex)
						.getPreferredSize().width);
			} catch (Throwable e) {
			}
		}
		return max.getProperWidth() + extraGap;
	}

	private static final boolean DEBUG = false;

	private static void debug(Object... msgs) {
		if (DEBUG) {
			for (Object m : msgs)
				System.out.print(m);
			System.out.println();
		}
	}
}
